package tech

import (
	"fmt"
	"ssp/set"
	"strings"
)

func (s *Solve) TripleCellForcingChain(g *Grid) (used bool, results []*Result) {
	return g.cellForcingChain(3)
}

func (s *Solve) QuadCellForcingChain(g *Grid) (used bool, results []*Result) {
	return g.cellForcingChain(4)
}

func (s *Solve) TripleHouseForcingChain(g *Grid) (used bool, results []*Result) {
	return g.houseForcingChain(3)
}

func (s *Solve) QuadHouseForcingChain(g *Grid) (used bool, results []*Result) {
	return g.houseForcingChain(4)
}

func (g *Grid) cellForcingChain(candNum int) (used bool, results []*Result) {
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.solved() || cell.cands.Count() != candNum {
				continue
			}
			if ok, itemsss := g.findCellForcingChains(cell); ok {
				used = true
				for _, itemss := range itemsss {
					rmItem := itemss[0][len(itemss[0])-1]
					result := new(Result)
					result.addRemoved(set.NewSet(rmItem.cand), rmItem.getCells())
					for _, items := range itemss {
						result.addHelpChains(strForcingChain(items))
					}
					if candNum == 3 {
						result.setStrategyName("Triple Cell Forcing Chain")
					} else {
						result.setStrategyName("Quad Cell Forcing Chain")
					}
					results = append(results, result)
				}
			}
		}
	}
	return
}

func (g *Grid) houseForcingChain(cellNum int) (used bool, results []*Result) {
	//for _, house := range append(append(g.boxes[:], g.rows[:]...), g.cols[:]...) {
	for _, house := range append(append(g.rows[:], g.boxes[:]...), g.cols[:]...) {
		//for _, house := range append(append(g.cols[:], g.rows[:]...), g.boxes[:]...) {
		for cand := range 9 {
			cells := []*Cell{}
			for _, cell := range house.cells {
				if !cell.solved() && cell.cands.Has(cand) {
					cells = append(cells, cell)
				}
			}
			if len(cells) != cellNum {
				continue
			}

			if ok, itemsss := g.findHouseForcingChains(cand, cells); ok {
				used = true
				for _, itemss := range itemsss {
					rmItem := itemss[0][len(itemss[0])-1]
					result := new(Result)
					result.addRemoved(set.NewSet(rmItem.cand), rmItem.getCells())
					for _, items := range itemss {
						result.addHelpChains(strForcingChain(items))
					}
					if cellNum == 3 {
						result.setStrategyName("Triple House Forcing Chain")
					} else {
						result.setStrategyName("Quad House Forcing Chain")
					}
					results = append(results, result)
				}
			}
		}
	}
	return
}

func strForcingChain(items []GroupItem) string {
	var b strings.Builder
	link := "-"
	for _, item := range items[:len(items)-1] {
		fmt.Fprintf(&b, "%v%v", item, link)
		if link == "=" {
			link = "-"
		} else {
			link = "="
		}
	}
	fmt.Fprintf(&b, "%v", items[len(items)-1])
	return b.String()
}

func (g *Grid) findCellForcingChains(cell *Cell) (used bool, itemsss [][][]GroupItem) {
	n := cell.cands.Count()
	nodess := make([][]*ChainNode, n)

	for i, cand := range cell.cands.GetList() {
		nodess[i] = g.travelTo(cand, cell)
	}
	commonss := getForcingChainCommonNodes(nodess)
	if len(commonss) == 0 {
		return
	}
	for _, commons := range commonss {
		itemss := make([][]GroupItem, n)
		for i, common := range commons {
			itemss[i] = getItemsFromNode(common)
		}
		itemsss = append(itemsss, itemss)
	}
	used = true
	return
}

func (g *Grid) findHouseForcingChains(cand int, cells []*Cell) (used bool, itemsss [][][]GroupItem) {
	n := len(cells)
	nodess := make([][]*ChainNode, n)

	for i, cell := range cells {
		nodess[i] = g.travelTo(cand, cell)
	}
	commonss := getForcingChainCommonNodes(nodess)
	if len(commonss) == 0 {
		return
	}
	for _, commons := range commonss {
		itemss := make([][]GroupItem, n)
		for i, common := range commons {
			itemss[i] = getItemsFromNode(common)
		}
		itemsss = append(itemsss, itemss)
	}
	used = true
	return
}

/*
	func getForcingChainCommonNodes(nodess [][]*ChainNode) (commonss [][]*ChainNode) {
		itemNodes := map[GroupItem][]*ChainNode{}
		for _, nodes := range nodess {
			for _, node := range nodes {
				if _, ok := itemNodes[node.item]; !ok {
					itemNodes[node.item] = []*ChainNode{}
				}
				itemNodes[node.item] = append(itemNodes[node.item], node)
			}
		}
		for _, nodes := range itemNodes {
			if len(nodes) == len(nodess) {
				commonss = append(commonss, nodes)
			}
		}
		return
	}
*/
func (g *Grid) travelTo(cand int, cell *Cell) (weakNodes []*ChainNode) {
	levelNum := 1
	root := &ChainNode{nil, GroupItem{cand, [9]*Cell{cell, nil, nil}}}
	curLevel := []*ChainNode{root}
	usedWeaks := map[GroupItem]bool{}
	usedStrongs := map[GroupItem]bool{}
	usedWeaks[root.item] = true
	usedStrongs[root.item] = true
	strongFlag := false
	for levelNum < 60 {
		//fmt.Println(levelNum, strongFlag)
		nextLevel := []*ChainNode{}
		for _, node := range curLevel {
			if strongFlag {
				strongs := g.aicALSGetStrongs(node)
				for _, strong := range strongs {
					if usedStrongs[strong] {
						continue
					}
					next := &ChainNode{node, strong}
					nextLevel = append(nextLevel, next)
					usedStrongs[strong] = true
				}
			} else {
				weaks := g.aicALSGetWeaks(node)
				for _, weak := range weaks {
					if usedWeaks[weak] {
						continue
					}
					next := &ChainNode{node, weak}
					nextLevel = append(nextLevel, next)
					weakNodes = append(weakNodes, next)
					usedWeaks[weak] = true
				}
			}
		}
		if len(nextLevel) == 0 {
			break
		}
		//for _, node := range nextLevel {
		//	fmt.Println("  ", node.item)
		//}
		//fmt.Println()
		curLevel = nextLevel
		strongFlag = !strongFlag
		levelNum++
	}

	return
}
